Server And Client

Learn how to implement a network server using asyncio in Python.

Since asyncio is exceptional at handling thousands of network connections, it provides a useful framework for implementing network servers. The following example implements a simplistic TCP server, but it is up to you to build any network server you might like.

Server and Client

Note: It is possible to build an asyncio based Web server using aiohttp. However, it is not that useful in most cases because it will often be slower than a fast, optimized, native WSGI server like uwsgi or gunicorn. Beacuse Python Web applications are always using WSGI, it is easy to switch out the WSGI server for a fast and asynchronous one.

Asyncio server#

Following is a simple example of a TCP server. This server listens for strings terminated by \n and returns them in upper case.

To run the following application (server), click Run and enter the command python asyncio-server.py.

To change the source code in the playground and run, click Run again after changing the code, then wait for the four-step process of container creation to complete. Then, click on the terminal to go back and issue the command to run the program again.

/
asyncio-server.py
asyncio TCP server

To implement a server, the first step is to define a class that inherits from asyncio.Protocol. It is not strictly necessary to inherit from this class, but it is a good idea to get all the basic methods defined – even if they do nothing.

The connection_made(transport) method is called as soon as a connection is established by a client. An asyncio.BaseTransport object is passed as the argument, which represents the underlying socket and stream with the client. This object offers several methods such as get_extra_info to get more information on the client, or close method, to close the transport. The connection_lost is the other end of the connection handling code. It is called when the connection is terminated. Both connection_made and connection_lost are called once per connection.

The data_received method is called each time some data is received. It might never be called if no data is ever received. The eof_received method might be called once if the client sends an EOF signal.

The following figure describes the state machine and the usual workflow that asyncio.Protocol provides.

asyncio.Protocol state machine
asyncio.Protocol state machine

The simplest way to test the above example is to use the netcat program, available as the nc command on most versions of Unix.

In the above application, after running the server, open another terminal and enter the command nc localhost 1234 to start communication with the server. Once the connection is made, enter lowercase character strings, press enter to send, and the server will return the same string in uppercase.

Typing any text followed by \n returns it in upper case. Sending EOF by pressing Control+d closes the connection.

Asyncio client#

We have been selling asyncio as extremely fast, so it is time to prove that point. The same asyncio.Protocol class can be used to implement a client.

To run the following application (server), clikc Run and enter command python asyncio-server.py. Then, open another terminal, move to the relevant folder containing asyncio-client.py using command cd examples/event-loops/, and then enter command python asyncio-client.py to start the client.

/
asyncio-client.py
asyncio-server.py
asyncio TCP client

The client in the above example connects to the YellEchoServer. Once connected, connection_made is called and the client sends its message via its talk method. The server replies back with that text in uppercase, and the data_received is called. In this case, the client talks again to the server, creating an infinite loop of interaction between the two.

Note: The messages that are sent from the client to server and vice versa are not printed so you will not see anything in the terminal. However, feel free to change the code to print those messages.

Printing stats#

This is useful to test the performance of the server. By modifying some bits of the above example and adding some statistics, we can have a rough idea of the amount our server can handle.

To run the following application (server), click Run and enter command python asyncio-server-stats.py. Then, open another terminal, and type cd examples/event-loops/. Then enter the command python asyncio-client.py to start the client.

To obtain the statistics, stop the server by entering ctrl + c after some time on the terminal where the server is running.

/
asyncio-client.py
asyncio-server-stats.py
asyncio TCP server with statistics

In the above example, basic statistics are stored, computed, and printed at the end of the program.

While running it on my laptop with five clients at the same time, the asyncio server is able to handle more than 23,000 messages per second. Obviously, this server is not doing much work – upper-casing a string is not that impressive – but this is still a pretty decent result. Keep in mind that this server is not using any thread or any extra processes, so it is only using one single CPU.

Asyncio is a transcendent solution to write asynchronous network clients and servers. The protocol implementation is straightforward, and the ability to mix all kinds of asynchronous workload makes the framework powerful.

Using Asyncio

Naoki Inada on asyncio